home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-07-11 | 21.1 KB | 517 lines | [TEXT/MPS ] |
- refbal.readme (version: Monday, July 10, 1995 1:15 PM)
- David McCusker
-
- === Overview =====================================================
-
- Refbal is an MPW tool which looks for ref-counting problems in C or C++
- sources for OpenDoc. It scans source files heuristically looking for
- patterns that look like problems and prints out error messages. The
- primary situation it understands is unbalanced acquires and releases inside
- an individual function. Refbal's main goal is to require balanced acquires
- and releases inside a function for every variable that is assigned an
- acquired value. If you acquire a value, refbal wants you to assign it
- to a variable, and then it wants you to release it. :-) In addition to this
- main goal, refbal also attempts to impose the requirement that acquired
- values only be returned from functions with "Acquire" in their names. To
- this end, returning a required value is generally an appropriate substitute
- for releasing it.
-
- Refbal is also concerned that you delete instances of objects, like
- ODStorageUnitView, which are not ref-counted themselves, but contain
- references to ref-counted objects they have acquired.
-
- Refbal is marginally useful to help find cross-function problems with
- ref-counting, but only by calling attention to those places where acquired
- values are not balanced in individual functions (because you are going
- to balance them across functions, right?). (Perhaps you should draw a
- data flow diagram to illustrate cross-function balances, with acquires
- as the source of an arrow, and requires as the sink.)
-
- The most common way in which acquired values are passed between functions
- is through assignment to member variables. Therefore, if you are not
- interested in seeing cross-function imbalances caused by member variables,
- the most effective way of shutting out this noise is to suppress analysis
- of variables which look like member variables. In the OpenDoc sources,
- member variable references typically begin with either /_/ or /f[A-Z]/,
- so refbal ignores variable names like this by default. If you want refbal
- to help you with balancing your member variable references, the most you
- can do is enable the error reports for these variables to help call your
- attention to these strategic points in your code. [Better support for
- developer's member variable names could have been provided by using
- regular expressions to describe them; but this seemed more complicated
- than warranted.]
-
- Sometimes refbal appears to be pretty stupid about C and C++ syntax, and
- this is because it *is* stupid about C and C++. It makes all judgments
- based on patterns that occur in just a few tokens of interest--mainly
- identifiers, braces, parens, semicolons, and the assignment operator.
-
- === Limitations ====================================================
- Refbal cannot find ref-counting problems that are not apparent from
- static analysis of individual functions. The most obvious case involves
- objects that are acquired in one function, but released in another.
-
- A less obvious ref-counting problem involves objects which are *not*
- ref-counted, but which refer to objects which *are* ref-counted. You
- must be certain to delete any such objects in your code, so they have
- an opportunity to release their ref-counted objects.
-
- ODFacet is an example of a non-ref-counted object which has references
- to ref-counted objects like ODFrame, ODShape, and ODCanvas. However, the
- OpenDoc API never gives a developer a pointer to a facet which should be
- directly deleted. Instead, a developer must be sure to use the OpenDoc API
- to remove all facets from an embedded frame, or otherwise the frame ref-count
- will not reach zero due to outstanding references that were not released.
-
- === Warnings You Can Ignore ========================================
- Suppose you have a member variable named _fContFrame which points to an
- ODFrame, and your code contains a code fragment like the following:
-
- ODFrame* containingFrame = frame->AcquireContainingFrame(ev);
- _fContFrame = containingFrame;
-
- Presumably _fContFrame assumes ownership of the reference that was
- acquired for containingFrame, and some other function will release
- the value that was assigned to _fContFrame. Refbal will complain
- that containingFrame was not released; but when you look at your
- code, you will see that containingFrame is subsequently assigned to
- a member variable. You should expect a number of such warnings in
- your code; they are safe to ignore provided you correctly release
- your member variables later.
-
- Similarly, refbal will probably complain about ref-counted objects
- which you acquire and put into a collection (like a list of
- frames), or remove from a collection and then release, because the
- acquires and releases do not occur all in a single function.
-
- Refbal also generates some unnecessary warnings due to simple inability
- to evaluate which sections of code are executed in conditional
- expressions. For example you might re-assign to a variable when it is
- nil, but to refbal this looks like you are losing a ref-count reference:
-
- ODFrame* containingFrame = frame->AcquireContainingFrame(ev);
- if ( !containingFrame )
- containingFrame = otherFrame->AcquireContainingFrame(ev);
-
- If references are released in one conditional branch, refbal warns it
- does not know whether the reference is released in all branches:
- ODFrame* containingFrame = frame->AcquireContainingFrame(ev);
- if ( condition )
- {
- // ... do something
- ODReleaseObject(ev, containingFrame);
- }
- else
- {
- // ... do something else
- ODReleaseObject(ev, containingFrame);
- }
-
- === Usage ==========================================================
- A typical command line to check file foo.c might look likes
-
- refbal -e foo.c
-
- Invoking refbal with no argument will cause usage info to be printed:
-
- ### Usage: refbal [option ...] filenames
- options:
- -a # obsolete option (once affected assignment warnings)
- -c # console - if not -u, echo file progress to stderr
- -e # echo - print lines associated with errors
- -f # fields - include information about fFoo fields
- -l # local - suppress warnings about releasing values not acquired locally
- -m # members - include information about '_' members
- -p # path - suppress warnings about execution paths
- -t # temps - suppress warnings about ODTemp's
- -u # unbuffered - write to stderr (not stdout)
- status:
- 0 # no error
- 1 # generic problem
- 2 # unknown option
- 3 # no filename specified
- 4 # cannot open file
- 9 # scope too deep
- 10 # generic file IO problem
- 11 # not enough memory
- 14 # internal error
- version: Monday, July 10, 1995 1:15 PM
-
- The -a option is obsolete.
-
- Use the -c option if you are redirecting output from examining several files,
- and you want to have file progress indications output to stderr. E.g.:
- refbal -c opendoc:ā.cā > opendoc.refbal
-
- Use the -e option if you want to have lines printed which contain
- problems that refbal is reporting. This option is generally recommended
- so you can hand-filter some of the warnings by examining the line which
- caused the warning.
-
- Use the -f and -m options if you want to re-enable the generation of
- errors associated with variables that begin with either /_/ or /f[A-Z]/.
-
- Use the -l and -p options to suppress warnings that might seem less
- useful, regarding release of variables not acquired locally (you
- probably just got one from a collection), or regarding release of
- variables in a different scope than the original acquire (refbal
- cannot do flow analysis).
-
- Use the -t option to suppress error messages related to temp variables
- (which have typenames beginning with either ODTemp or TempOD). This is a
- good idea if you routinely swap values in and out of temp variables, or
- even release them because you know what you are doing. Such usage may
- confuse refbal and it may generate many errors because it thinks
- temp variables should release themselves.
-
- === Example ========================================================
- Suppose we run refbal on a file foo.c containing the following function.
- (Note that it is just a random assemblage of stuff that will not compile.
- You should make sure code you scan at least compiles before you run refbal
- on it; why confuse refbal more than necessary?)
-
- ODFoo* MyClass:GetFoo( )
- {
- ODFoo* returnedFoo = fetch->AcquireFoo( ); // returned, not released
- ODFoo* droppedFoo = drop->AcquireFoo( ); // never released
- TempODFoo tempFoo = temp->AcquireFoo( ); // auto-releases
-
- ODFoo* origFoo = orig->AcquireFoo( ); // released by ODCopyAndRelease
- ODFoo* copiedFoo = ODCopyAndRelease(ev, origFoo); // never released
-
- // presumably other member functions release the value acquired next:
- _fIgnoredFoo = x->AcquireFoo( ); // SOM member variable
-
- unknown->Release(); // was never acquired locally
-
- random->AcquireFoo()->Random(); // did not assign result of AcquireFoo()
-
- ODBeer* frameBeer;
- if ( flag->AcquireBool() ) // do not put Acquires in parentheses
- {
- frameBeer = _fFrame->AcquireMoreBeer(ev, kODNULL); // not released
- }
- else
- {
- frameBeer = _fFrame->AcquireBeer(ev, kODNULL); // released
- ODFoo* copiedForm = xform->Copy(ev); // never released
- ODFinalReleaseObject(ev, frameBeer);
- }
- ODReleaseObject(ev, tempFoo); // okay to release temp type this way
- tempFoo->Release( ); // but not okay to release temp this way
-
- if (arbitrary)
- return returnedFoo; // function GetFoo( ) does not contain "Acquire"
-
- ODReleaseObject(ev, returnedFoo); // releasing a returned value
-
- frameShape = RequestFrameShape(); // not released
- borderShape = AdjustBorderShape(); // not released
- baseMenuBar = CopyBaseMenuBar(); // not released
-
- myWindow = AcquireWindow();
- myWindow = AcquireWindow(); // second assignment without a release
-
- myWindow->Close(); // releases myWindow
-
- myWindow->CloseAndRemove(); // already released by Close()
-
- ODStorageUnitView* suv = foobar->CreateView(); // not deleted
-
- return 0L;
- }
-
- If you run refbal on foo.c with the -e option, you will get
- output that looks something like the following. This example illustrates
- many of the kinds of apparent problems that refbal understands.
-
- refbal -e foo.c
- %% foo.c ...
- ### warning: releasing unknown which was not locally acquired
- File "foo.c"; Line 13
- unknown->Release(); // was never acquired locally
- ### ERROR: must assign result of AcquireFoo()
- File "foo.c"; Line 15
- random->AcquireFoo()->Random(); // did not assign result of AcquireFoo()
- ### ERROR: cannot track Acquire "AcquireBool" inside parens
- File "foo.c"; Line 18
- if ( flag->AcquireBool() ) // do not put Acquires in parentheses
- ### warning: re-assigning frameBeer not released since previous assign
- File "foo.c"; Line 24
- frameBeer = _fFrame->AcquireBeer(ev, kODNULL); // released
- File "foo.c"; Line 20 # frameBeer assigned from AcquireMoreBeer()
- frameBeer = _fFrame->AcquireMoreBeer(ev, kODNULL); // not released
- ### ERROR: copiedForm was not released
- File "foo.c"; Line 25 # copiedForm assigned from Copy()
- ODFoo* copiedForm = xform->Copy(ev); // never released
- ### warning: releasing tempFoo (temp vars auto-release)
- File "foo.c"; Line 29
- tempFoo->Release( ); // but not okay to release temp this way
- File "foo.c"; Line 5 # tempFoo assigned from AcquireFoo()
- TempODFoo tempFoo = temp->AcquireFoo( ); // auto-releases
- ### ERROR: returning acquired value returnedFoo from non-acquire function GetFoo
- File "foo.c"; Line 32
- return returnedFoo; // function GetFoo( ) does not contain "Acquire"
- ### warning: releasing returnedFoo returned earlier
- File "foo.c"; Line 34
- ODReleaseObject(ev, returnedFoo); // releasing a returned value
- File "foo.c"; Line 3 # returnedFoo assigned from AcquireFoo()
- ODFoo* returnedFoo = fetch->AcquireFoo( ); // returned, not released
- ### warning: re-assigning myWindow not released since previous assign
- File "foo.c"; Line 41
- myWindow = AcquireWindow(); // second assignment without a release
- File "foo.c"; Line 40 # myWindow assigned from AcquireWindow()
- myWindow = AcquireWindow();
- ### ERROR: myWindow has already been released by Close()
- File "foo.c"; Line 45
- myWindow->CloseAndRemove(); // already released by Close()
- File "foo.c"; Line 41 # myWindow assigned from AcquireWindow()
- myWindow = AcquireWindow(); // second assignment without a release
- ### ERROR: baseMenuBar was not released
- File "foo.c"; Line 38 # baseMenuBar assigned from CopyBaseMenuBar()
- baseMenuBar = CopyBaseMenuBar(); // not released
- ### ERROR: borderShape was not released
- File "foo.c"; Line 37 # borderShape assigned from AdjustBorderShape()
- borderShape = AdjustBorderShape(); // not released
- ### ERROR: frameShape was not released
- File "foo.c"; Line 36 # frameShape assigned from RequestFrameShape()
- frameShape = RequestFrameShape(); // not released
- ### ERROR: droppedFoo was not released
- File "foo.c"; Line 4 # droppedFoo assigned from AcquireFoo()
- ODFoo* droppedFoo = drop->AcquireFoo( ); // never released
- ### ERROR: copiedFoo was not released
- File "foo.c"; Line 8 # copiedFoo assigned from ODCopyAndRelease()
- ODFoo* copiedFoo = ODCopyAndRelease(ev, origFoo); // never released
- ### ERROR: suv was not deleted
- File "foo.c"; Line 47 # suv assigned from CreateView()
- ODStorageUnitView* suv = foobar->CreateView(); // not deleted
-
-
- === Syncing when lost ================================================
- What might confuse refbal? What happens when refbal gets confused?
- How does it sync up to continue parsing a file?
-
- If refbal encounters unbalanced braces (which might not bother a compiler
- which understands ifdef's), then refbal will not know how to judge
- when variables go out of scope. If refbal becomes confused, subsequent
- error messages may be inappropriate until it re-syncs.
-
- Refbal assumes that a left brace '{' occurring as the first character
- on a line is an indication of a function beginning, and will try to
- re-sync when it sees one. (This seemed like a harmless assumption;
- please tell me all your functions place { on the beginning of new lines.)
-
- Refbal tries to ignore the extra braces caused by extern "C":
-
- #ifdef __cplusplus
- extern "C" {
- #endif
-
- === Acquire Function Names ===========================================
-
- Refbal might generate what seems like an excessive number of warnings.
- It tries to err on the side of too many, as opposed to too few, warnings.
- A likely cause of spurious warnings is the names of functions which
- look like an "acquire" function according to the heuristic rules that
- refbal applies. If some of your function names fit certain rules,
- refbal will assume that you are returning an acquired ref-counted
- object and will warn that you should assign and subsequently release the
- returned value. To make this situation less confusing, below is a
- description of exactly what qualifies as a refbal "acquire" function call.
-
- Let id be the identifier used in a function call. For example, in
-
- TempODStorageUnit su = draft->AcquireDraftProperties(ev);
-
- id is "AcquireDraftProperties". A series of tests is applied to id
- to determine whether it is an acquire, or not an acquire function. Some
- of the tests are to exclude what otherwise would be taken as an acquire
- function. The tests are ordered, when possible, to take advantage of
- quick rejections in order to speed up processing.
-
- The expression "contains" means a simple substring test below, but
- "discretely contains" (a made-up term) means something slightly more
- specific: when id discretely contains "Acquire", this means that
- "Acquire" is a substring of id, and further that the next character
- is not an uppercase letter. Thus FocusAcquired() does *not* discretely
- contain "Acquire" because of the trailing "d". Thus, "discrete" means
- a discrete word in a function name.
-
- Inclusions:
- id is considered an acquire function if
- * id is exactly equal to one of the following
- Acquire
- AdjustBorderShape
- ConstructRealPart
- Copy
- CopyBaseMenuBar
- FindODWindow
- ODAcquireObject
- ODCopyAndRelease
- RegisterWindow
- RegisterWindowForFrame
- RequestEmbeddedFrame
- RequestFrameShape
- RetrievePersistentObject
-
- * id discretely contains "Acquire"
-
- * id contains one of the following
- AdjustBorderShape
- RequestEmbeddedFrame
- RequestFrameShape
-
- * id discretely contains a verb before a noun, where verbs are one of
- Create Make New
- and where nouns are one of
- Container Draft Link Part Storage Window
- Document Frame MenuBar Shape Transform
-
- * the previous identifier is "new" and id is one of the following
- CMDocument ODBaseLinkSource ODFileContainer ODPersistentObject
- CMDraft ODBaseShape ODFrame ODSemanticInterface
- CMStorageUnit ODBaseTransform ODLink ODSettingsExtension
- Clock ODBentoContainer ODLinkSource ODShape
- ClockSI ODContainer ODLinkSpec ODShellPlugIn
-
- Container ODDocument ODMemContainer ODStorageUnit
- DragText ODDraft ODMenuBar ODTransform
- DrawSI ODEditorSetup ODPart ODWindow
- NoPart ODEmbeddedContainer ODPartWrapper ShellSI
- ODBaseLink ODExtension ODPartsBin SkeletonPart
-
- ODFacet ODCanvas ODStorageUnitView
-
- Exclusions:
- id is *not* an acquire function if
- * id is exactly equal to one of the following
- NewCWindow CreateRootFrame CreateLinkSpec CreateNewLink
- CreateStandardPartToken
- * id contains one of
- Iter Prox Desc Edit parent_
-
- === Parsing ==========================================================
-
- Here is a more detailed specification of the tokens that refbal considers:
-
- * ; (the statement terminator)
- * = (the assignment operator)
- * parentheses
- * braces
- * left braces '{' which are the first character on a line
- * identifiers
- * identifiers containing one of the following substrings
- Acquire ODTemp TempOD Init
-
- AdjustBorderShape
- RequestEmbeddedFrame
- RequestFrameShape
-
- Create Make New
- Container Draft Link Part Storage Window
- Document Frame MenuBar Shape Transform
-
- Iter Prox Desc Edit parent_
-
- * identifiers exactly equal to one of the following
- new delete return
- Release Acquire Copy
- ODReleaseObject ODFinalReleaseObject ODSafeReleaseObject
- ODDeleteObject ODCopyAndRelease ODAcquireObject
-
- SOM_TRY SOM_CATCH_ALL SOM_ENDTRY
- TRY CATCH_ALL ENDTRY
-
- Close CloseAndRemove
-
- AdjustBorderShape
- ConstructRealPart
- CopyBaseMenuBar
- FindODWindow
- RegisterWindow
- RegisterWindowForFrame
- RequestEmbeddedFrame
- RequestFrameShape
- RetrievePersistentObject
-
- CreateView CreateCanvas
-
- CMDocument ODBaseLinkSource ODFileContainer ODPersistentObject
- CMDraft ODBaseShape ODFrame ODSemanticInterface
- CMStorageUnit ODBaseTransform ODLink ODSettingsExtension
- Clock ODBentoContainer ODLinkSource ODShape
- ClockSI ODContainer ODLinkSpec ODShellPlugIn
-
- Container ODDocument ODMemContainer ODStorageUnit
- DragText ODDraft ODMenuBar ODTransform
- DrawSI ODEditorSetup ODPart ODWindow
- NoPart ODEmbeddedContainer ODPartWrapper ShellSI
- ODBaseLink ODExtension ODPartsBin SkeletonPart
-
- ODFacet ODCanvas ODStorageUnitView
-
- RequestFrameShape AdjustBorderShape RequestEmbeddedFrame
-
- NewCWindow CreateRootFrame CreateLinkSpec CreateNewLink
- CreateStandardPartToken
-
- * identifiers which begin with "_" (SOM "members")
- * identifiers which begin with "f[A-Z]" (C++ "fields")
-
- Conversely, refbal carefully ignores the following:
-
- * identifiers exactly equal to one of the following
- ev, if, else, for, while, case, switch
- * The characters '*' and '&', which are removed with the white space.
- * C and C++ comments both: /* */ and //
- * the contents of strings enclosed by double quotes.
-
- Given the information above, it becomes possible to view code through
- the eyes that refbal uses. Consider the following piece of code:
-
- SOM_Scope ODTransform* SOMLINK ODFacetAcquireContentTransform(
- ODFacet *somSelf, Environment *ev,
- ODCanvas* biasCanvas)
- {
- ODFacetData *somThis = ODFacetGetData(somSelf);
- ODFacetMethodDebug("ODFacet","AcquireContentTransform");
-
- SOM_TRY
- ODTransform* foo = kODNULL;
-
- if (!_fContentTransform)
- {
- foo = _fFrame->AcquireInternalTransform(ev, kODNULL);
- _fContentTransform = foo->Copy(ev);
- ODReleaseObject(ev, foo);
- foo = somSelf->AcquireFrameTransform(ev, kODNULL);
- _fContentTransform->PostCompose(ev, foo);
- ODReleaseObject(ev, foo);
- }
- return BiasTransformGet(ev, _fContentTransform, biasCanvas);
- SOM_CATCH_ALL
- SOM_ENDTRY
- return kODNULL;
- }
-
- To refbal, the above code looks identical to the code below. Note how
- much C and C++ information is left out. (Admittedly, some of it is left
- out because refbal doesn't think it is interesting when it looks at it.)
-
- ODFacetAcquireContentTransform( )
- {
- {
- ODTransform foo = ;
-
- {
- foo = AcquireInternalTransform ;
- _fContentTransform = Copy ;
- ODReleaseObject( foo );
- foo = AcquireFrameTransform ;
- ODReleaseObject( foo );
- }
- }
- }
-
- <readme end of file>
-